feat(custom-block): move management to enterprise settings; derive inputs live from deployed start#5453
feat(custom-block): move management to enterprise settings; derive inputs live from deployed start#5453TheodoreSpeaks wants to merge 10 commits into
Conversation
…puts live from deployed start
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Inputs are stored only as placeholder overrides in Runtime/UI behavior: disabled blocks stay resolvable on canvas ( Query invalidation broadens to all custom-block lists after mutations. Reviewed by Cursor Bugbot for commit b07b062. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
@greptile review |
| notDeployed || | ||
| iconUpload.isUploading || | ||
| deployed.isLoading || | ||
| (deployedLoaded && visibleOutputs.length === 0) |
There was a problem hiding this comment.
Legacy blocks cannot update
Medium Severity
The detail view requires at least one curated output to enable Save and rejects empty exposedOutputs. Blocks published with no outputs (previously “all outputs / result”) keep exposedOutputs: [] and cannot be edited or updated in the new settings UI.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 34108b0. Configure here.
Greptile SummaryThis PR moves custom-block creation/editing from the Deploy modal into a dedicated Enterprise settings page, changes
Confidence Score: 4/5The serializer, handler, and operations changes are well-structured and covered by new tests; the main risk is the still-open cross-workspace cache collision in useCustomBlocks (flagged in a prior review and not yet addressed) which could serve stale blocks during a workspace switch. The lifecycle hardening (dropped blocks, filtered inputs, JSON decode) is correct and well-tested. The new settings UI wires up inputs/outputs cleanly with live deployment derivation. The pre-existing useCustomBlocks query key omits workspaceId while the queryFn still scopes by workspace — a user who switches between two org workspaces within the stale window sees the first workspace's custom blocks in the palette of the second. apps/sim/hooks/queries/custom-blocks.ts — the query key needs a workspaceId segment to prevent cross-workspace cache collisions. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Admin as Admin (Settings UI)
participant API as /api/custom-blocks
participant DB as custom_block (DB)
participant Loader as CustomBlocksLoader
participant Registry as Block Registry
participant Executor as WorkflowBlockHandler
Admin->>API: POST /api/custom-blocks (workflowId, name, inputs[], outputs[])
API->>DB: "INSERT with inputs=placeholders[], outputs=[]"
DB-->>API: new row
API-->>Admin: CustomBlockWithInputs
Note over Loader,Registry: On workspace mount
Loader->>API: "GET /api/custom-blocks?workspaceId=..."
API->>DB: SELECT all rows (enabled + disabled)
DB-->>API: rows[]
API-->>Loader: CustomBlock[] (inputFields derived live from deployed Start)
Loader->>Registry: "hydrateClientCustomBlocks(all, hideFromToolbar=!enabled)"
Note over Executor: At run-time
Executor->>Executor: isCustomBlock? → checkChildDeployment (outside try)
Executor->>Executor: remapCustomBlockInputKeys (id→name, decode JSON values)
Executor->>Executor: execute child under source owner's identity/env
Executor->>Executor: projectCustomBlockOutput (curated outputs only)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Admin as Admin (Settings UI)
participant API as /api/custom-blocks
participant DB as custom_block (DB)
participant Loader as CustomBlocksLoader
participant Registry as Block Registry
participant Executor as WorkflowBlockHandler
Admin->>API: POST /api/custom-blocks (workflowId, name, inputs[], outputs[])
API->>DB: "INSERT with inputs=placeholders[], outputs=[]"
DB-->>API: new row
API-->>Admin: CustomBlockWithInputs
Note over Loader,Registry: On workspace mount
Loader->>API: "GET /api/custom-blocks?workspaceId=..."
API->>DB: SELECT all rows (enabled + disabled)
DB-->>API: rows[]
API-->>Loader: CustomBlock[] (inputFields derived live from deployed Start)
Loader->>Registry: "hydrateClientCustomBlocks(all, hideFromToolbar=!enabled)"
Note over Executor: At run-time
Executor->>Executor: isCustomBlock? → checkChildDeployment (outside try)
Executor->>Executor: remapCustomBlockInputKeys (id→name, decode JSON values)
Executor->>Executor: execute child under source owner's identity/env
Executor->>Executor: projectCustomBlockOutput (curated outputs only)
Reviews (3): Last reviewed commit: "fix(custom-block): keep disabled blocks ..." | Re-trigger Greptile |
Greptile SummaryThis PR moves custom-block management out of the Deploy modal into a dedicated Enterprise Settings page, stores only per-input placeholder hints in the DB (deriving the full input field set live from the deployed Start block so removed inputs can never linger), hardens the lifecycle so deleted custom blocks are dropped gracefully instead of corrupting workflows, and fixes image-icon sizing across the canvas, toolbar, and command palette.
Confidence Score: 3/5The lifecycle and serializer changes are well-tested and correct, but the query-key change introduces a cross-workspace cache issue that should be resolved before merging. The mutation invalidations rightly use apps/sim/hooks/queries/custom-blocks.ts — the Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Admin as Admin (Settings UI)
participant API as /api/custom-blocks
participant DB as custom_block (DB)
participant Start as Deployed Start Block
participant Consumer as Consumer Workflow
Admin->>API: "POST publish {workflowId, name, inputs:[{id,placeholder}], exposedOutputs}"
API->>DB: "INSERT custom_block (inputs=placeholders only)"
API->>Start: deriveInputFields(workflowId)
Start-->>API: "[{id,name,type}...]"
API-->>Admin: block with live inputFields (placeholders merged)
Admin->>API: "PATCH update {inputs, exposedOutputs}"
API->>DB: UPDATE inputs column
Consumer->>API: GET /api/custom-blocks
API->>DB: SELECT custom_block JOIN workflow JOIN workspace
API->>Start: deriveInputFields per enabled block
Start-->>API: live field set
API-->>Consumer: "inputFields = live fields + stored placeholders merged"
Consumer->>Consumer: serialize workflow
Note over Consumer: isCustomBlockType + !getBlock → drop block + edges
Consumer->>Consumer: remapCustomBlockInputKeys (drop stale, decode JSON)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Admin as Admin (Settings UI)
participant API as /api/custom-blocks
participant DB as custom_block (DB)
participant Start as Deployed Start Block
participant Consumer as Consumer Workflow
Admin->>API: "POST publish {workflowId, name, inputs:[{id,placeholder}], exposedOutputs}"
API->>DB: "INSERT custom_block (inputs=placeholders only)"
API->>Start: deriveInputFields(workflowId)
Start-->>API: "[{id,name,type}...]"
API-->>Admin: block with live inputFields (placeholders merged)
Admin->>API: "PATCH update {inputs, exposedOutputs}"
API->>DB: UPDATE inputs column
Consumer->>API: GET /api/custom-blocks
API->>DB: SELECT custom_block JOIN workflow JOIN workspace
API->>Start: deriveInputFields per enabled block
Start-->>API: live field set
API-->>Consumer: "inputFields = live fields + stored placeholders merged"
Consumer->>Consumer: serialize workflow
Note over Consumer: isCustomBlockType + !getBlock → drop block + edges
Consumer->>Consumer: remapCustomBlockInputKeys (drop stale, decode JSON)
|
…re inputs by authored data only
…ResourceRow + shared reserved-param set
…s fail loudly instead of vanishing
|
@greptile review |
…settings # Conflicts: # packages/db/migrations/meta/0255_snapshot.json # packages/db/migrations/meta/_journal.json
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5b73abb. Configure here.
…aceholders; reseed edit form after async load


Summary
custom_block.inputs; always derive the input field set (name/type) live from the deployed Start, so an input removed from the source can't lingerType of Change
Testing
Tested manually against a live deployed workflow. Added unit tests: serializer drops a deleted custom block + its edges; handler remap drops non-existent fields and decodes json inputs.
bun run lint,check:api-validation:strict, andcheck:migrations origin/stagingall pass.Checklist